﻿// AIrcClient.cs is part of AGatherBot.
//
// AGatherBot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 as
// published by the Free Software Foundation.
//
// AGatherBot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public version 2 License for more details.
//
// You should have received a copy of the GNU General Public License
// along with AGatherBot. If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using AGatherBot;
using AIrcClient.Commands;
using IrcDotNet;

namespace AIrcClient
{
    public class AIrcClient : IChatClient, IDisposable, IPartImportsSatisfiedNotification
    {
        #region Properties

        [Import(AllowDefault = true)]
        public IAuthService AuthService { get; private set; }

        /// <summary>
        /// Gets the IRC client.
        /// </summary>
        public IrcClient Client
        {
            get;
            private set;
        }

        public AGatherManager Parent
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the name of the IRC client.
        /// </summary>
        public string Name
        {
            get
            {
                return (this.Client.LocalUser.NickName);
            }
        }

        public Dictionary<string, IAccount> Users { get; private set; }

        #endregion

        #region Events

        public event EventHandler<ChatMessageEventArgs> OnPrivateMessage = delegate { };
        public event EventHandler<ChatMessageEventArgs> OnChannelMessage = delegate { };

        #endregion

        /// <summary>
        /// 
        /// </summary>
        public void OnImportsSatisfied()
        {
            // If there's an authentication service, make sure the client is the current one.
            if (this.AuthService != null)
            {
                this.AuthService.Client = this;
            }
        }

        #region Constructors

        public AIrcClient()
        {
            Program.RegisterCommand(new IrcConnectCommand(this));
            Program.RegisterCommand(new IrcDisconnectCommand(this));
            Program.RegisterCommand(new IrcRawCommand(this));

            this.Users = new Dictionary<string, IAccount>(StringComparer.OrdinalIgnoreCase);
        }

        #endregion

        /// <summary>
        /// Connects the IRC client to the IRC server.
        /// </summary>
        /// <exception cref="">Connection already exists.</exception>
        public void Connect()
        {
            // If the client already exist, throw an error.
            if (this.Client != null)
            {
                // TODO: Fix this exception.
                throw new Exception("Already Exists");
            }

            // Create a new client to connect to.
            var client = new IrcClient();

            // Build the registeration info.
            var regInfo = new IrcUserRegistrationInfo();
            regInfo.NickName = Properties.Settings.Default.IrcNick;
            regInfo.UserName = Properties.Settings.Default.IrcUserName;
            regInfo.Password = Properties.Settings.Default.IrcPassword;
            regInfo.RealName = Properties.Settings.Default.IrcRealName;

            // Some properties and events for our client.
            client.FloodPreventer   = new IrcStandardFloodPreventer(4, 2000);
            client.Registered       += IrcClient_Registered;

            // Wait until connection has succeeded or timed out.
            using (var connectedEvent = new ManualResetEventSlim(false))
            {
                client.Connected += (s, e) => connectedEvent.Set();

                client.Connect(
                    Properties.Settings.Default.IrcServer,
                    Properties.Settings.Default.IrcPort,
                    Properties.Settings.Default.IrcUseSsl,
                    regInfo);

                if (!connectedEvent.Wait(10000))
                {
                    client.Dispose();
                    client = null;
                    //ConsoleUtilities.WriteError("Connection to '{0}' timed out.", server);
                    return;
                }
            }

            this.Client = client;
        }

        private void IrcClient_Registered(object sender, EventArgs eArgs)
        {
            var client = (IrcClient)sender;

            this.AuthService.Auth();

            client.Channels.Join(Properties.Settings.Default.MainChannel);
            client.LocalUser.MessageReceived += LocalUser_MessageReceived;
            client.LocalUser.JoinedChannel      += IrcClient_LocalUser_JoinedChannel;
            client.LocalUser.LeftChannel        += IrcClient_LocalUser_LeftChannel;
            client.RawMessageReceived           += client_RawMessageReceived;
        }

        void LocalUser_MessageReceived(object sender, IrcMessageEventArgs e)
        {
            var user = this.GetAccountByNick(e.Source.Name);
            this.OnPrivateMessage(this, new ChatMessageEventArgs(user, e.Targets[0].Name, e.Text));
        }

        void client_RawMessageReceived(object sender, IrcRawMessageEventArgs e)
        {
            if (e.Message.Command == "354")
            {
                if (e.Message.Parameters[2] != "0")
                {
                    //var user = this.Parent.AccountSystem.GetUser();
                    //user.ChatId = e.Message.Parameters[1];
                }
                // TODO: Check if this works?
            }
        }

        private void IrcClient_LocalUser_JoinedChannel(object sender, IrcChannelEventArgs e)
        {
            if (e.Channel.Name.Equals(Properties.Settings.Default.MainChannel, StringComparison.OrdinalIgnoreCase))
            {
                e.Channel.MessageReceived   += Channel_MessageReceived;
                e.Channel.UserJoined        += Channel_UserJoined;
                this.Client.SendRawMessage("who " + e.Channel.Name + " n%na");

                // TODO: Set a variable stating we're expecting a who reply.
            }
        }

        private void Channel_UserJoined(object sender, IrcChannelUserEventArgs e)
        {

        }

        private void Channel_MessageReceived(object sender, IrcMessageEventArgs e)
        {
            var user = this.GetAccountByNick(e.Source.Name);
            var eventArgs = new ChatMessageEventArgs(user, e.Targets[0].Name, e.Text);

            this.OnChannelMessage(this, eventArgs);
        }

        private void IrcClient_LocalUser_LeftChannel(object sender, IrcChannelEventArgs e)
        {
            if (e.Channel.Name.Equals(Properties.Settings.Default.MainChannel, StringComparison.OrdinalIgnoreCase))
            {
                e.Channel.MessageReceived -= Channel_MessageReceived;
                e.Channel.UserJoined -= Channel_UserJoined;
            }
        }

        private IAccount GetAccountByNick(string nick)
        {
            if (!this.Users.ContainsKey(nick))
            {
                this.Users.Add(nick, this.Parent.AccountSystem.CreateEmptyUser());
                this.Users[nick].ChatNick = nick;
            }

            return (this.Users[nick]);
        }

        /// <summary>
        /// Disconnects the IRC client from the IRC server.
        /// </summary>
        public void Disconnect()
        {
            if (this.Client != null)
            {
                this.Client.Disconnect();
                this.Client.Dispose();
                this.Client = null;
            }
        }

        /// <summary>
        /// Sends a message to the specified targets.
        /// </summary>
        /// <param name="targets">The targets to send the message to.</param>
        /// <param name="message">The message to send.</param>
        public void SendMessage(IEnumerable<IAccount> targets, string message)
        {
            var chatTargets = from t in targets select t.ChatId;

            this.Client.LocalUser.SendMessage(chatTargets, message);
        }

        // TODO: standardize this shit.
        public void Dispose()
        {
            if (this.Client != null)
            {
                this.Disconnect();
                this.Client.Dispose();
                this.Client = null;
            }
        }
    }
}
